﻿using System;
using System.Collections.Generic;
using Microsoft.Practices.Unity;
using System.ServiceModel;
using Microsoft.Practices.Unity.Configuration;
using WDA.Config;
using WDA.Core.DynamicReconfiguration;
using System.ServiceModel.Description;
using Microsoft.Practices.Unity.InterceptionExtension;
using System.Configuration;

namespace WDA.Core
{
	/// <summary>
	/// WDAB runtime module for hosting services locally.
	/// </summary>	
	internal class LocalServicesModule : IModule
	{
		#region Fields
      private readonly List<IDisposable> _hosts = new List<IDisposable>();		
		private IUnityContainer _localContainer;
		private IUnityContainer _outerContainer;
		private IUnityContainer _servantContainer;
		private bool _disposed;

		private string _configName;
		private string _hostName;	   

		private static readonly Logger _logger = new Logger(typeof(LocalServicesModule));
		#endregion

		#region IModule Members
		public void Init(IUnityContainer outerContainer, IUnityContainer localContainer, IUnityContainer persistentContainer, string configName, string hostName)
		{
			_logger.WriteInfo("Initializing Local Services Module...");
			_localContainer = localContainer;
			_outerContainer = outerContainer;

			_configName = configName;
			_hostName = hostName;

			_servantContainer = new UnityContainer();		
			_servantContainer.RegisterInstance(WDAContainerNames.InternalContainerName, _localContainer);
			_servantContainer.RegisterInstance(WDAContainerNames.PersistentInternalContainerName, persistentContainer);
			_servantContainer.RegisterInstance(_servantContainer);
		   _servantContainer.AddNewExtension<Interception>();         
			_localContainer.RegisterInstance(WDAContainerNames.ServantContainerName, _servantContainer);

		   IEnumerable<IModuleConfigurator> moduleConfig = _outerContainer.ResolveAll<IModuleConfigurator>(); 
         foreach (IModuleConfigurator cfg in moduleConfig)
         {
            if (cfg.ShouldApply(WDAContainerNames.ServantContainerName))
            {
               cfg.Apply(_servantContainer);
            }
         }

			_logger.WriteInfo("Local Services Module initialized.");
		}

		public void CreateServiceControllers(IConfiguration config, Dictionary<Type, IServiceController> serviceControllers)
		{			
			IHost host = null;
			List<string> remoteHostMgmtAddresses = new List<string>();

			foreach (IHost h in config.Hosts)
			{
				if (h.ManagementUri != null)
				{
					remoteHostMgmtAddresses.Add(h.ManagementUri);
				}
				if (h.Name == _hostName)
				{
					host = h;
				}
			}

			string thisHostMgmtAddress = host.ManagementUri;

			foreach (IService svc in host.Services)
			{
				Type serviceType = Type.GetType(svc.InterfaceName, true);
				Type monitorType = Type.GetType(svc.MonitorInterfaceName, true);
				Type monitorFacadeType = ((MonitorFacadeAttribute)monitorType.GetCustomAttributes(typeof(MonitorFacadeAttribute), false)[0]).Type;
				Type serviceImpl = Type.GetType(svc.ImplementationName, true);

				string baseServiceUri = host.BaseUri;
				string baseMonitorUri = host.BaseMonitorUri;
				if (svc.BasePort != null)
				{
					baseServiceUri = baseServiceUri.Replace("#PORT#", svc.BasePort.ToString());
					baseMonitorUri = baseMonitorUri.Replace("#PORT#", (svc.BasePort + 1).ToString());
				}

				Uri serviceAddress = new Uri(!string.IsNullOrEmpty(svc.Uri) ? svc.Uri : string.Format("{0}/{1}", baseServiceUri, svc.Name));
            Uri monitorAddress = new Uri(!string.IsNullOrEmpty(svc.MonitorUri) ? svc.MonitorUri : string.Format("{0}/{1}", baseMonitorUri, svc.Name));

				object serviceMonitor = _outerContainer.Resolve(monitorType);
				_localContainer.RegisterInstance(monitorType, svc.Name, serviceMonitor);

			   IServiceHostFactory hostFactory = _outerContainer.Resolve<IServiceHostFactory>();

			   IDisposable monitorHost = hostFactory.CreateMonitorHost(monitorFacadeType, serviceMonitor, monitorAddress);            
			   IDisposable srvHost = hostFactory.CreateServiceHost(svc.Name, serviceType, serviceImpl, serviceAddress, _localContainer);            
            ServiceBehaviorAttribute localBehavior = new ServiceBehaviorAttribute();

				LocalServiceController ctrl = _localContainer.BuildUp(new LocalServiceController(serviceType, svc.Name, localBehavior));
				serviceControllers[serviceType] = ctrl;

            _servantContainer.RegisterType(serviceType, serviceImpl, svc.Name, new ExternallyControlledLifetimeManager());
            
            _servantContainer.Configure<Interception>()
               .AddPolicy(Guid.NewGuid().ToString()).AddMatchingRule(new TypeMatchingRule(serviceType))
               .AddCallHandler(new InterceptionContextSetterCallHandler(svc.Name, serviceType, serviceImpl));

			   _servantContainer.Configure<Interception>().SetDefaultInterceptorFor(serviceType, new InterfaceInterceptor2());

				_hosts.Add(monitorHost);
				_hosts.Add(srvHost);
				
				_logger.WriteInfo("Bringing up host for service {0} listening at {1}.", svc.Name, serviceAddress);
            _logger.WriteInfo("Bringing up monitor host for service {0} listening at {1}.", svc.Name, monitorAddress);
			}
			StartManagementHost(remoteHostMgmtAddresses, thisHostMgmtAddress);
		}
		#endregion

		#region Utility
		/// <summary>
		/// Uruchamia hosta usługi wymiany danych zarzadczych.
		/// </summary>
		/// <param name="remoteHostMgmtAddresses">Kolekcja adresów usług zarządczych innych hostów.</param>
		/// <param name="thisHostMgmtAddress">Adres zarządczy tego hosta.</param>
		private void StartManagementHost(List<string> remoteHostMgmtAddresses, string thisHostMgmtAddress)
		{
			IBindingFactory bindingFactory = _outerContainer.Resolve<IBindingFactory>();

			InterHostInterface ihi = new InterHostInterface(remoteHostMgmtAddresses);
			_localContainer.RegisterInstance(_localContainer.BuildUp<InterHostInterface>(ihi));
			ServiceHost mgmtHost = new ServiceHost(ihi);
			ServiceBehaviorAttribute mgmtHostBehavior = mgmtHost.Description.Behaviors.Find<ServiceBehaviorAttribute>();
			mgmtHostBehavior.IncludeExceptionDetailInFaults = true;
			mgmtHostBehavior.InstanceContextMode = InstanceContextMode.Single;
			mgmtHost.AddServiceEndpoint(typeof(IInterHostInterface), bindingFactory.CreateBinding(), thisHostMgmtAddress);
			_hosts.Add(mgmtHost);
			mgmtHost.Open();
		}
		#endregion

		#region IDisposable Members
		public void Dispose()
		{
			Dispose(true);
		}
		public virtual void Dispose(bool disposing)
		{
			if (!_disposed)
			{
				if (disposing)
				{
					foreach (IDisposable host in _hosts)
					{
						host.Dispose();
					}
					_disposed = true;
				}
			}
			GC.SuppressFinalize(this);
		}
		#endregion
	}
}
